package global

import (
	"context"
	"encoding/hex"
	"fmt"
	"github.com/mark3labs/mcp-go/server"
	"math/rand"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/qiniu/qmgo"

	"github.com/flipped-aurora/gin-vue-admin/server/utils/timer"
	"github.com/songzhibin97/gkit/cache/local_cache"

	"golang.org/x/sync/singleflight"

	"go.uber.org/zap"

	"github.com/flipped-aurora/gin-vue-admin/server/config"

	"github.com/redis/go-redis/v9"
	"github.com/spf13/viper"
	"gorm.io/gorm"
)

var (
	GVA_DB                     *gorm.DB
	GVA_DBList                 map[string]*gorm.DB
	GVA_REDIS                  redis.UniversalClient
	GVA_REDISList              map[string]redis.UniversalClient
	GVA_MONGO                  *qmgo.QmgoClient
	GVA_CONFIG                 config.Server
	GVA_VP                     *viper.Viper
	GVA_LOG                    *zap.Logger
	GVA_Timer                  = timer.NewTimerTask()
	GVA_Concurrency_Control    = &singleflight.Group{} // 不是真正的"锁"机制，而是请求合并器，主要用于防止缓存击穿
	GVA_Concurrency_Control_V2 ConcurrencyController   // redis实现的分布式并发锁
	GVA_ROUTERS                gin.RoutesInfo
	GVA_ACTIVE_DBNAME          *string
	GVA_MCP_SERVER             *server.MCPServer
	BlackCache                 local_cache.Cache
	lock                       sync.RWMutex
	LocTime                    *time.Location //本地时间时区 东八区时区对象
)

// ConcurrencyController 并发控制器接口
type ConcurrencyController interface {
	// Do 在互斥锁保护下执行函数
	Do(ctx context.Context, key string, fn func() (interface{}, error)) (interface{}, error)
}

// InitConcurrencyControl 初始化并发控制器
func InitConcurrencyControl() {
	if GVA_REDIS == nil {
		GVA_LOG.Warn("Redis客户端未初始化，无法初始化并发控制器")
		return
	}

	GVA_Concurrency_Control_V2 = NewRedisConcurrencyController(GVA_REDIS)
	GVA_LOG.Info("并发控制器初始化完成")
}

// RedisConcurrencyController 基于Redis的分布式锁并发控制器
type RedisConcurrencyController struct {
	redisClient redis.UniversalClient
	timeout     time.Duration
}

// NewRedisConcurrencyController 创建Redis并发控制器实例
func NewRedisConcurrencyController(redisClient redis.UniversalClient) *RedisConcurrencyController {
	return &RedisConcurrencyController{
		redisClient: redisClient,
		timeout:     30 * time.Second, // 默认锁超时时间
	}
}

// generateLockID 生成唯一的锁ID
func generateLockID() string {
	b := make([]byte, 16)
	_, err := rand.Read(b)
	if err != nil {
		// 如果随机数生成失败，使用时间戳作为备选
		return fmt.Sprintf("lock_%d", time.Now().UnixNano())
	}
	return hex.EncodeToString(b)
}

// Do 实现ConcurrencyController接口的Do方法
func (rc *RedisConcurrencyController) Do(ctx context.Context, key string, fn func() (interface{}, error)) (interface{}, error) {
	// 生成锁键名
	lockKey := fmt.Sprintf("lock:%s", key)
	// 生成唯一的锁ID
	lockID := generateLockID()

	// 尝试获取锁
	success, err := rc.redisClient.SetNX(ctx, lockKey, lockID, rc.timeout).Result()
	if err != nil {
		GVA_LOG.Error("获取分布式锁失败", zap.String("key", key), zap.Error(err))
		return nil, fmt.Errorf("获取分布式锁失败: %w", err)
	}

	// 如果获取锁失败，直接返回错误
	if !success {
		GVA_LOG.Warn("分布式锁已存在，操作被跳过", zap.String("key", key))
		return nil, fmt.Errorf("操作正在执行中，请稍后再试")
	}

	// 确保锁被释放
	defer func() {
		// 使用Lua脚本原子性地检查并删除锁，避免误删其他客户端的锁
		luaScript := `
		if redis.call("get", KEYS[1]) == ARGV[1] then
			return redis.call("del", KEYS[1])
		else
			return 0
		end
		`
		result, err := rc.redisClient.Eval(ctx, luaScript, []string{lockKey}, lockID).Result()
		if err != nil {
			GVA_LOG.Error("释放分布式锁失败", zap.String("key", key), zap.Error(err))
		} else if result.(int64) == 0 {
			GVA_LOG.Warn("分布式锁可能已过期或被其他客户端持有", zap.String("key", key))
		}
	}()

	// 执行函数
	return fn()
}

// GetGlobalDBByDBName 通过名称获取db list中的db
func GetGlobalDBByDBName(dbname string) *gorm.DB {
	lock.RLock()
	defer lock.RUnlock()
	return GVA_DBList[dbname]
}

// MustGetGlobalDBByDBName 通过名称获取db 如果不存在则panic
func MustGetGlobalDBByDBName(dbname string) *gorm.DB {
	lock.RLock()
	defer lock.RUnlock()
	db, ok := GVA_DBList[dbname]
	if !ok || db == nil {
		panic("db no init")
	}
	return db
}

func GetRedis(name string) redis.UniversalClient {
	redis, ok := GVA_REDISList[name]
	if !ok || redis == nil {
		panic(fmt.Sprintf("redis `%s` no init", name))
	}
	return redis
}
